package cloudspace.vm.javassist;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
import java.lang.reflect.Modifier;

/**
 * The Class ConfigParser. This class is responsible for parsing configurations.
 * The config parser will parse a configuration file once. this config parser
 * should be kept as a wrapper for the configuration file. The parser will
 * automatically update if the underlying configurationfile has been updated.
 */
public class ConfigParser
{

	/** The time stamp. */
	private long timeStamp;

	/** The config file. */
	private File configFile;

	/** The expr editors. */
	private List<CommandInterface> exprEditors;
	private Map<String, CtClass> customClasses;
	/** The start command. */
	Pattern startCommand;
	private ClassPool proxyPool;

	/**
	 * Instantiates a new config parser.
	 * 
	 * @param file
	 *            the file to use as a backend configuration.
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public ConfigParser(File file, ClassPool pPool) throws IOException
	{
		proxyPool = pPool;
		customClasses = new HashMap<String, CtClass>();
		startCommand = Pattern
				.compile("replace|insertAfter|insertBefore|define");
		configFile = file;
		timeStamp = configFile.lastModified();
		if (timeStamp == 0)
		{
			throw new FileNotFoundException();
		}
		parseEditors();
	}

	/**
	 * This will look for updates to the configuration file and automatically
	 * update the editors underneath
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public void autoUpdate() throws IOException
	{
		if (timeStamp != configFile.lastModified())
		{
			parseEditors();
			timeStamp = configFile.lastModified();
		}
	}

	private String grepUntillEOC(Scanner confScanner, String EOC)
	{
		Pattern endOfCommand = Pattern.compile(EOC);
		String fullCommand = "";
		while (confScanner.hasNext() && !confScanner.hasNext(endOfCommand))
		{
			String commandPart = confScanner.next();
			fullCommand += " ";
			fullCommand += commandPart;
		}
		confScanner.next();
		return fullCommand;
	}

	private void parseEditors() throws IOException
	{

		Pattern comment = Pattern.compile("^//.*");
		Pattern translationType = Pattern.compile("translate");
		Pattern macroType = Pattern.compile("define");
		Pattern proxyType = Pattern.compile("proxy");
		exprEditors = new ArrayList<CommandInterface>();
		FileReader inputStream = new FileReader(configFile);
		Scanner configScanner = new Scanner(inputStream);
		while (configScanner.hasNext())
		{

			if (configScanner.hasNext(comment))
			{
				configScanner.nextLine();
				continue;
			}
			else if (configScanner.hasNext(translationType))
			{
				configScanner.next();
				createCommand(configScanner);
			}
			else if (configScanner.hasNext(macroType))
			{
				try
				{
					MacroDefinition curMacro = createMacro(configScanner);
					CommandFactory.registerMacro(curMacro);
				}
				catch (MalformedMacroException e)
				{
					// TODO: Decide if this should be passed up further.
					e.printStackTrace();
				}

			}
			else if (configScanner.hasNext(proxyType))
			{
				createProxy(configScanner);
			}
			else
			{
				configScanner.next();
			}

		}
	}

	private void createProxy(Scanner configScanner)
	{
		configScanner.next();
		String proxyClass = configScanner.next();
		int lastAccessor = proxyClass.lastIndexOf('.');
		String newClassName = "auto.generated._"
				+ proxyClass.substring(lastAccessor + 1);
		CtClass pClass = proxyPool.makeClass(newClassName);
		try
		{

			CtClass actualClass = proxyPool.get(proxyClass);
			CtMethod[] methods = actualClass.getMethods();
			CtConstructor[] constructors = actualClass.getConstructors();
			for (CtConstructor constr : constructors)
			{
				if (Modifier.isPublic(constr.getModifiers()))
				{
					CtClass[] paramTypes = constr.getParameterTypes();
					String methodSig = "public static " + actualClass.getName()
							+ " _constructor(";
					int argNum = 0;
					for (CtClass param : paramTypes)
					{
						methodSig += param.getName();
						methodSig += " arg" + argNum;
						methodSig += ",";
						argNum++;
					}
					if (paramTypes.length != 0)
						methodSig = methodSig.substring(0,
								methodSig.length() - 1);
					methodSig += ")";
					CtClass[] execeptionTypes = constr.getExceptionTypes();
					if (execeptionTypes.length != 0)
					{
						methodSig += " throws ";
					}

					for (CtClass exception : execeptionTypes)
					{
						methodSig += exception.getName() + ",";
					}
					if (execeptionTypes.length != 0)
						methodSig = methodSig.substring(0,
								methodSig.length() - 1);
					methodSig += "{return new " + actualClass.getName() + "(";
					for (int i = 0; i < paramTypes.length; i++)
					{
						methodSig += "arg" + i;
						if (i < paramTypes.length - 1)
							methodSig += ",";

					}
					methodSig += ");}";
					try
					{
						CtMethod proxyMethod = CtNewMethod.make(methodSig,
								pClass);
						pClass.addMethod(proxyMethod);
					}
					catch (CannotCompileException e)
					{
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				}
			}
			for (CtMethod method : methods)
			{
				if (Modifier.isPublic(method.getModifiers()))
				{
					String methodName = method.getName();
					String returnType = method.getReturnType().getName();
					CtClass[] paramTypes = method.getParameterTypes();
					String methodSig = "public static " + returnType + " _"
							+ methodName + "(";
					int argNum = 0;
					if (!Modifier.isStatic(method.getModifiers()))
					{
						methodSig += proxyClass + " arg0";
						if (paramTypes.length != 0)
						{
							methodSig += ",";
						}
						argNum++;
					}
					for (int i = 0; i < paramTypes.length; i++)
					{
						methodSig += paramTypes[i].getName();
						methodSig += " arg" + argNum;
						argNum++;
						if (i + 1 < paramTypes.length)
						{
							methodSig += ",";
						}
					}
					methodSig += ")";
					CtClass[] execeptionTypes = method.getExceptionTypes();
					if (execeptionTypes.length != 0)
					{
						methodSig += " throws ";
					}

					for (CtClass exception : execeptionTypes)
					{
						methodSig += exception.getName() + ",";
					}
					if (execeptionTypes.length != 0)
						methodSig = methodSig.substring(0,
								methodSig.length() - 1);
					methodSig += "{";
					if (!returnType.equals("void"))
						methodSig += "return ";
					argNum = 0;
					if (Modifier.isStatic(method.getModifiers()))
					{
						methodSig += actualClass.getName() + "." + methodName
								+ "(";
					}
					else
					{
						methodSig += "arg" + argNum + "." + methodName + "(";
						argNum++;
					}
					for (int i = 0; i < paramTypes.length; i++)
					{
						methodSig += "arg" + argNum;
						if (i + 1 < paramTypes.length)
							methodSig += ",";
						argNum++;
					}
					methodSig += ");}";
					try
					{
						CtMethod proxyMethod = CtNewMethod.make(methodSig,
								pClass);
						pClass.addMethod(proxyMethod);
					}
					catch (CannotCompileException e)
					{
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				}
			}
		}
		catch (NotFoundException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		customClasses.put(proxyClass, pClass);
	}

	private MacroDefinition createMacro(Scanner configScanner)
			throws MalformedMacroException
	{
		configScanner.next();
		String macro = configScanner.nextLine().trim();
		Pattern quotePat = Pattern.compile("\\<\\<\\s+[a-zA-Z0-9]+");
		String quotation = null;
		Matcher quoteMatch = quotePat.matcher(macro);
		if (quoteMatch.find())
		{
			quotation = quoteMatch.group();

		}
		if (quotation != null)
		{
			String commandPart = macro.substring(
					macro.indexOf(quotation) + quotation.length()).trim();
			macro = macro.substring(0, macro.indexOf(quotation)).trim() + " \"";
			quotation = quotation.substring(2).trim();
			// Pattern endOfCommandPattern = Pattern.compile(quotation);

			if (commandPart.contains(quotation))
			{
				commandPart = commandPart.substring(0, commandPart
						.indexOf(quotation));
			}
			else
			{
				commandPart += grepUntillEOC(configScanner, quotation) + "\"";
				/*
				 * while(!configScanner.hasNext(endOfCommandPattern)) {
				 * commandPart += configScanner.next(); } configScanner.next();
				 * }
				 */
				macro += " " + commandPart;
			}
		}

		MacroDefinition curMacro = new MacroDefinition(macro);
		return curMacro;
	}

	private void createCommand(Scanner configScanner)
	{
		Pattern beginOfCommand = Pattern.compile("<<|\\\".*");
		List<String> args = new ArrayList<String>();
		boolean end = false;
		while (configScanner.hasNext() && !end)
		{
			while (!configScanner.hasNext(beginOfCommand))
			{
				args.add(configScanner.next());
			}

			if (configScanner.hasNext(beginOfCommand))
			{
				if (configScanner.hasNext("\\\".*"))
				{
					String commandSection = configScanner.nextLine();
					commandSection = commandSection.trim();
					args.add(commandSection);
					end = true;
				}
				else if (configScanner.hasNext("<<"))
				{
					configScanner.next();
					String commandEnd = configScanner.next();
					String fullCommand = "\""
							+ grepUntillEOC(configScanner, commandEnd) + "\"";
					args.add(fullCommand);
					end = true;
				}
			}
			else
			{
				String fullCommand = configScanner.nextLine();
				args.add(fullCommand);
				end = true;
			}

		}
		try
		{
			CommandInterface curCommand = CommandFactory.createCommand(args);
			exprEditors.add(curCommand);
		}
		catch (MalformedCommandException e)
		{
			System.err.println("Bad command:  " + args);
			e.printStackTrace();
		}
		catch (MalformedMacroException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Gets all of the commands located in a configuration file.
	 * 
	 * @return the commands parsed from the configuration file.
	 */
	public List<CommandInterface> getCommands()
	{
		try
		{
			autoUpdate();
		}
		catch (IOException e)
		{
			System.err.println("Config file is no longer valid");
			e.printStackTrace();
		}
		return exprEditors;
	}

	public CtClass getProxy(String className)
	{
		return customClasses.get(className);

	}

}
